//Copyright (c) 2023 靳灿奇
//ideaLicenseServerSearch is licensed under Mulan PubL v2.
//You can use this software according to the terms and conditions of the Mulan PubL v2.
//You may obtain a copy of Mulan PubL v2 at:
//         http://license.coscl.org.cn/MulanPubL-2.0
//THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
//EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
//MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
//See the Mulan PubL v2 for more details.

package main

import (
	"container/list"
	"context"
	"encoding/xml"
	"fmt"
	"github.com/fatih/color"
	"github.com/gin-gonic/gin"
	"io"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
)

var (
	logPrePanic    = color.New(color.FgRed, color.Bold).Sprint("[PANIC]")
	logPreError    = color.New(color.FgRed).Sprint("[ERROR]")
	logPreWarn     = color.New(color.FgYellow).Sprint("[WARN]")
	logPreInfo     = color.New(color.FgGreen).Sprint("[INFO]")
	logPreDebug    = color.New(color.FgBlue).Sprint("[DEBUG]")
	LogPanic       = log.New(os.Stdout, logPrePanic, log.LstdFlags).Panicf
	LogError       = log.New(os.Stdout, logPreError, log.LstdFlags).Printf
	LogWarn        = log.New(os.Stdout, logPreWarn, log.LstdFlags).Printf
	LogInfo        = log.New(os.Stdout, logPreInfo, log.LstdFlags).Printf
	LogDebug       = log.New(os.Stdout, logPreDebug, log.LstdFlags).Printf
	DomainNameList = []string{
		"http://jetbrains-licw.novx.org",
		"http://jetbrains-lic.novx.org",
		"https://yncts-lic.gotoweb.top",
		"https://jb.samuraism.com",
		"https://jetbrains.wf-wolves.de",
		"http://200.13.89.3:8080",
		"https://jetbrains.uberinternal.com",
		"https://jetbrainslic.xogrp.com",
		"http://134.53.225.196",
		"http://200.17.85.220",
		"http://134.68.246.32",
		"http://85.143.112.153",
		"http://147.228.63.4:8080",
		"http://5.2.198.177:8001",
		"http://yoda.bhasvic.ac.uk",
	}
	// LicensePools ticketId -> domainName
	LicensePools = sync.Map{}
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			LogError("%v", err)
		}
	}()

	// Create and start the server
	r := gin.Default()
	// Register the route and handler for the GET rpc request
	r.GET("/rpc/obtainTicket.action", func(c *gin.Context) {
		queue := list.New()
		queueLock := sync.Mutex{}
		quit := make(chan struct{})
		wg := sync.WaitGroup{}
		ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
		defer cancel()
		for _, v := range DomainNameList {
			wg.Add(1)
			go func(domainName string) {
				defer wg.Done()
				resp, err := http.Get(fmt.Sprintf("%s/rpc/ping.action?salt=%d", domainName, time.Now().UnixNano()))
				if err != nil {
					LogWarn("%v", err)
					return
				}
				defer resp.Body.Close()
				body, err := io.ReadAll(resp.Body)
				if err != nil {
					LogWarn("%v", err)
					return
				}
				var pingResponse struct {
					ResponseCode string `xml:"responseCode"`
				}
				err = xml.Unmarshal(body, &pingResponse)
				if err != nil {
					LogWarn("%v", err)
					return
				}
				if pingResponse.ResponseCode == "OK" {
					queueLock.Lock()
					queue.PushBack(domainName)
					queueLock.Unlock()
				}
			}(v)
		}
		go func() {
			wg.Wait()
			close(quit)
		}()
	outerLoop:
		for {
			select {
			case <-ctx.Done():
				break outerLoop
			case <-quit:
				break outerLoop
			}
		}
		if queue.Len() == 0 {
			c.JSON(http.StatusRequestTimeout, gin.H{"error": "timeout"})
		}
		client := http.DefaultClient
		queueLock.Lock()
		defer queueLock.Unlock()
		for e := queue.Front(); e != nil; e = e.Next() {
			domainName := e.Value.(string)
			queue.Remove(e)
			resp, err := client.Get(fmt.Sprint(domainName, c.Request.URL.Path, "?", c.Request.URL.RawQuery))
			if err != nil {
				LogError("%v", err)
				continue
			}
			body, err := io.ReadAll(resp.Body)
			if err != nil {
				LogError("%v", err)
				continue
			}
			var obtainTicketResponse struct {
				ResponseCode string `xml:"responseCode"`
				TicketId     string `xml:"ticketId"`
			}
			err = xml.Unmarshal(body, &obtainTicketResponse)
			if err != nil {
				LogError("%v", err)
				continue
			}
			if obtainTicketResponse.ResponseCode == "OK" {
				LicensePools.Store(obtainTicketResponse.TicketId, domainName)
				c.Data(http.StatusOK, "application/xml", body)
				return
			}
		}
		//TODO 临时日志 当没有任何一个许可证服务器可用时，打印这个日志
		LogDebug("%v", "ticket is not available now")
		c.JSON(http.StatusOK, gin.H{"error": "ticket is not available now"})
	})

	r.GET("/rpc/releaseTicket.action", func(c *gin.Context) {
		domainName, ok := LicensePools.Load(c.Query("ticketId"))
		if !ok {
			c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
			return
		}

		resp, err := http.Get(fmt.Sprint(domainName, c.Request.URL.Path, "?", c.Request.URL.RawQuery))
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		var releaseTicketResponse struct {
			ResponseCode string `xml:"responseCode"`
		}
		err = xml.Unmarshal(body, &releaseTicketResponse)
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		if releaseTicketResponse.ResponseCode == "OK" {
			LicensePools.Delete(c.Query("ticketId"))
			c.Data(http.StatusOK, "application/xml", body)
			return
		}
		LogError("%v", "release ticket failed")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
	})

	r.GET("/rpc/prolongTicket.action", func(c *gin.Context) {
		domainName, ok := LicensePools.Load(c.Query("ticketId"))
		if !ok {
			c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
			return
		}

		resp, err := http.Get(fmt.Sprint(domainName, c.Request.URL.Path, "?", c.Request.URL.RawQuery))
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		var releaseTicketResponse struct {
			ResponseCode string `xml:"responseCode"`
		}
		err = xml.Unmarshal(body, &releaseTicketResponse)
		if err != nil {
			LogError("%v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
			return
		}
		if releaseTicketResponse.ResponseCode == "OK" {
			c.Data(http.StatusOK, "application/xml", body)
			return
		}
		LogError("%v", "prolong ticket failed")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
	})
	if err := r.Run(":8081"); err != nil {
		LogError("%v", err)
	}
}
